package ir;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DataNode extends ClassGenerator {

  protected List<DataMethodNode> methodNodes;
  protected List<FieldNode> fieldNodes;
  
  public DataNode(Class<Object> dataClass) {
    
    super(dataClass);
    
    methodNodes = new ArrayList<DataMethodNode>();
    fieldNodes = new ArrayList<FieldNode>();
    
    Method[] declaredMethods = underlier.getDeclaredMethods();
    
    for (Method m : declaredMethods) {
        DataMethodNode methodNode = new DataMethodNode(m);
        
        
        // For Setters, get the Type of the set variable.
        // Creates a new private field of that Type and Name.
        // e.g. setMarketGroupID(long id) will generate a new
        // field called MarketGroupID of type long.
        FieldNode f = methodNode.getSetFieldNode();
        if (f != null) {
          addFieldNode(f);
        }
        
        addMethodNode(methodNode);
        
    }
  }
  
  private void addMethodNode(DataMethodNode methodNode) {
    methodNodes.add(methodNode);
  }

  private void addFieldNode(FieldNode f) {
    fieldNodes.add(f);
  }

  //Get all the Data Classes used; anything in params or returns or in Fields
  @Override
  protected Set<Class<Object>> getRequiredTypes() {
    
     Set<Class<Object>> types = new HashSet<Class<Object>>();
     types.addAll(super.getRequiredTypes());
     
     // fields of the data node
     Field[] declaredFields = underlier.getDeclaredFields();
     for (Field f : declaredFields) {
       String fname = f.getClass().getName();
       if (! fname.contains("axis")) {
        Class fieldClass = f.getClass();
        types.add(fieldClass);
      }
     }
     
      return types;
   }

  @Override
  public Set<DataNode> getRequiredDataNodes() {
    
    // Retrieve Data Types assumed to be pre-requisite;
    // goes thru all methods for return and argument types.
    Set<Class<Object>> requiredTypes = getRequiredTypes();
    Set<DataNode> requiredNodes = new HashSet<DataNode>();
    
    // convert to DataNodes
    for (Class<Object> dataClass : requiredTypes) {
      
      // get component type if it's an array. 
      Class<Object> candidateClass;
      if (dataClass.isArray()) {
        candidateClass = (Class<Object>) dataClass.getComponentType();
      }
      else candidateClass = dataClass;
      
      // filter for primitives and strings
      if (! candidateClass.isPrimitive() && ! 
          candidateClass.equals(String.class)) {
        if (! candidateClass.getCanonicalName().contains("axis") && 
            !candidateClass.getCanonicalName().contains("java")) {
          
          DataNode dataNode;
          //TODO new class extending datanode that works exclusively for these enum types
          if (candidateClass.getConstructors().length == 0 ) {
//            System.out.println("DATANODE: getRequiredDataNodes:" + candidateClass.getSimpleName());
            dataNode = new EnumDataNode(candidateClass);
          }
          else 
            dataNode = new DataNode(candidateClass);
          
          requiredNodes.add(dataNode);
        }
      }
      
    }
    return requiredNodes;
  }
  
  /**
   * Generate class for this data type: all getters, setters, and private fields.
   */
  @Override
  public String gen() {
    
    String generated = "";
    
    // generate field code
    for (MemberNode n : fieldNodes)
      if (!n.gen().equals(""))
        generated = "  " + n.gen() + generated;
    
    // generate method code
    for (MemberNode n : methodNodes)
      if (!n.gen().equals(""))
        generated += "  " + n.gen();
    
    // generate imports
    String imports = getImports();
    
    String constructor = INDENT + "public " + getGeneratedClassName() + "() {} \n";
    
    generated = imports + "\n" + "public class " + getGeneratedClassName() + 
      " implements Serializable { \n" + constructor + generated + " \n } \n";
    
    generated = getPackageDeclaration() + generated;
    
    return generated + "\n";
  }

  protected String getPackageDeclaration() {
    return "package " + getPackage() + ";\n";
  }

  public String generateGwt2Soap() {
    // <Datatype.name> Gwt2Soap ( Gwt<Datatype.Name> in ) {
    //      <DataType.name> out = new <DataType.name>();
    //      out.set<FIELD> ( in.get<FIELD> )
    //              ... etc.
    //      return out;
    //  }
    
    String name = underlier.getSimpleName();
    String method_head = INDENT + "public " + name + " " + "Gwt2Soap" + " (" + getGeneratedClassName() + " in) {\n" +
      INDENT + INDENT + name + " out = new " + name + "();\n";
    
    String method_body = "";
    
    for (FieldNode f : fieldNodes) {
      method_body += INDENT + INDENT + f.genGwt2Soap();
    }
    
    method_body += INDENT + INDENT + "return out;\n";
    
    method_body += INDENT + "}\n\n";
    
    return method_head + method_body;
      
  }
  
  @Override
  public String getPackage() {
    return "com.google.client.types";
  }
  
  public String generateSoap2Gwt() {
    // Gwt<Datatype.name> Soap2Gwt ( <Datatype.Name> in ) {
    //      Gwt<DataType.name> out = new Gwt<DataType.name>();
    //      out.set<FIELD> ( in.get<FIELD> )
    //              ... etc.
    //      return out;
    //  }
    
    String underlierName = underlier.getSimpleName();
    
    String method_head = INDENT + "public " + getGeneratedClassName() + " " + "Soap2Gwt" + " (" + underlierName + " in) {\n" +
      INDENT + INDENT + getGeneratedClassName() + " out = new " + getGeneratedClassName() + "();\n";
    
    String method_body = "";
    
    // translation
    for (FieldNode f : fieldNodes) {
      method_body += INDENT + INDENT + f.genSoap2Gwt();
    }
    
    method_body += INDENT + INDENT + "return out;\n";
    
    method_body += INDENT + "}\n\n";
    
    return method_head + method_body;
      
  }
  
  /**
   * AdData[] -> GwtAdData[]
   * foreach AdData, GwtAdData[i] = Soap2Gwt(AdData)
   * @return
   */
  public String generateArraySoap2Gwt() {

    // input: AdData
    String arr_in = getUnderlier().getSimpleName() + "[]";
    String method_args = "(" + arr_in + " in)";
    
    // form the method sig
    // Return: GwtAdData[]
    String method_return_type = getGeneratedClassName() + "[]";
    String method_head = "public " + method_return_type + " Soap2Gwt" + method_args;
    
    
    // form method logic of unpacking, translating, repacking
    String arr_out = method_return_type + " out";
    String declare_arr_out = INDENT + INDENT + arr_out + " = new " + getGeneratedClassName() + "[in.length];\n";

    String packer = INDENT + INDENT + "for (int i = 0; i < in.length; i++) { out[i] = Soap2Gwt( in[i] ); }\n";
    String returning = INDENT + INDENT + "return out;\n";
    String method_body = declare_arr_out + packer + returning;
    
    String method = INDENT + method_head + INDENT + "{\n" + method_body + INDENT + "}\n\n";
    
    return method;
  }
  
  /**
   * GwtAdData[] -> AdData[]
   * foreach GwtAdData, AdData[] = Gwt2Soap(GwtAdData)
   * @return
   */
  public String generateArrayGwt2Soap() {
 // input: AdData
    String arr_in = getGeneratedClassName() + "[]";
    String method_args = "(" + arr_in + " in)";
    
    // form the method sig
    // Return: GwtAdData[]
    String method_return_type = getUnderlier().getSimpleName() + "[]";
    String method_head = "public " + method_return_type + " Gwt2Soap" + method_args;
    
    
    // form method logic of unpacking, translating, repacking
    String arr_out = method_return_type + " out";
    String declare_arr_out = INDENT + INDENT + arr_out + " = new " + getUnderlier().getSimpleName() + "[in.length];\n";

    String packer = INDENT + INDENT + "for (int i = 0; i < in.length; i++) { out[i] = Gwt2Soap( in[i] ); }\n";
    String returning = INDENT + INDENT + "return out;\n";
    String method_body = declare_arr_out + packer + returning;
    
    String method = INDENT + method_head + INDENT + "{\n" + method_body + INDENT + "}\n\n";
    
    return method;
  }


}
